/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using DaveSexton.DocProject.Resources;
using DaveSexton.DocProject.Configuration;
using EnvDTE;
using System.Windows.Forms;
using EnvDTE80;

namespace DaveSexton.DocProject
{
	public abstract class ToolWindow
	{
		#region Public Properties
		public Guid Guid
		{
			get
			{
				return guid;
			}
		}

		public abstract string Caption { get; }

		public abstract Control Control { get; }

		public virtual bool ShowOnLoad
		{
			get
			{
				return false;
			}
		}

		public bool Visible
		{
			get
			{
				return window.Visible;
			}
			set
			{
				if (window == null)
					throw new InvalidOperationException(Errors.ToolWindowNotCreated);

				window.Visible = value;
			}
		}

		public virtual bool IsLinkable
		{
			get
			{
				return false;
			}
		}

		public virtual bool IsFloating
		{
			get
			{
				return false;
			}
		}

		public bool Showing
		{
			get
			{
				return showing;
			}
		}

		public bool HasFocus
		{
			get
			{
				return hasFocus;
			}
		}

		public virtual System.Drawing.Bitmap Image
		{
			get
			{
				return null;
			}
		}

		[CLSCompliant(false)]
		public Window Window
		{
			get
			{
				return window;
			}
			internal set
			{
				window = value;

				if (window != null)
				{
					// NOTE: do not pass in window or else the WindowActivated event won't be raised for deactivation as well as activation
					events = ((Events2) VSEnvironmentHost.DTE.Events).get_WindowEvents(null);

					events.WindowActivated += WindowActivated;
					// not raised (at least not for tool windows that are treated like documents):
					/* events.WindowClosing += WindowClosing;
					 * events.WindowCreated += WindowCreated_Handler;
					 * events.WindowMoved += WindowMoved;
					*/

					visibilityEvents = ((Events2) VSEnvironmentHost.DTE.Events).get_WindowVisibilityEvents(window);
					// not raised: visibilityEvents.WindowShowing += WindowShowing;
					visibilityEvents.WindowHiding += WindowHiding;

					OnWindowCreated(EventArgs.Empty);
				}
				else if (visibilityEvents != null)
				{
					events.WindowActivated -= WindowActivated;
					events = null;

					visibilityEvents.WindowHiding -= WindowHiding;
					visibilityEvents = null;

					OnWindowDestroyed(EventArgs.Empty);
				}
			}
		}
		#endregion

		#region Private / Protected
		private readonly Guid guid;
		private Window window;
		private WindowVisibilityEvents visibilityEvents;
		private WindowEvents events;
		/// <summary>
		/// VS doesn't manage visibility and window events properly so it's managed internally by this class.  
		/// The actual visible state of a tool window is reflected in this field and exposed via the public
		/// <see cref="Showing"/> property.
		/// </summary>
		private bool showing, hasFocus;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="ToolWindow" /> class for derived types.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "guid")]
		protected ToolWindow(Guid guid)
		{
			this.guid = guid;
		}
		#endregion

		#region Methods
		#endregion

		#region Events
		private readonly object WindowCreatedEventLock = new object();
		private EventHandler WindowCreatedEvent;
		private readonly object WindowDestroyedEventLock = new object();
		private EventHandler WindowDestroyedEvent;
		private readonly object ShowEventLock = new object();
		private EventHandler ShowEvent;
		private readonly object HideEventLock = new object();
		private EventHandler HideEvent;
		private readonly object ActivatedEventLock = new object();
		private EventHandler ActivatedEvent;
		private readonly object DeactivatedEventLock = new object();
		private EventHandler DeactivatedEvent;

		/// <summary>
		/// Event raised when the <see cref="Window"/> loses focus.
		/// </summary>
		public event EventHandler Deactivated
		{
			add
			{
				lock (DeactivatedEventLock)
				{
					DeactivatedEvent += value;
				}
			}
			remove
			{
				lock (DeactivatedEventLock)
				{
					DeactivatedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="Deactivated" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnDeactivated(EventArgs e)
		{
			EventHandler handler = null;

			lock (DeactivatedEventLock)
			{
				handler = DeactivatedEvent;
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="Window"/> is focused.
		/// </summary>
		public event EventHandler Activated
		{
			add
			{
				lock (ActivatedEventLock)
				{
					ActivatedEvent += value;
				}
			}
			remove
			{
				lock (ActivatedEventLock)
				{
					ActivatedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="Activated" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnActivated(EventArgs e)
		{
			EventHandler handler = null;

			lock (ActivatedEventLock)
			{
				handler = ActivatedEvent;
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="Window"/> is hidden.
		/// </summary>
		public event EventHandler Hide
		{
			add
			{
				lock (HideEventLock)
				{
					HideEvent += value;
				}
			}
			remove
			{
				lock (HideEventLock)
				{
					HideEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="Hide" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnHide(EventArgs e)
		{
			EventHandler handler = null;

			lock (HideEventLock)
			{
				handler = HideEvent;
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="Window"/> becomes visible.
		/// </summary>
		public event EventHandler Show
		{
			add
			{
				lock (ShowEventLock)
				{
					ShowEvent += value;
				}
			}
			remove
			{
				lock (ShowEventLock)
				{
					ShowEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="Show" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnShow(EventArgs e)
		{
			EventHandler handler = null;

			lock (ShowEventLock)
			{
				handler = ShowEvent;
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="Window"/> is destroyed.
		/// </summary>
		public event EventHandler WindowDestroyed
		{
			add
			{
				lock (WindowDestroyedEventLock)
				{
					WindowDestroyedEvent += value;
				}
			}
			remove
			{
				lock (WindowDestroyedEventLock)
				{
					WindowDestroyedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="WindowDestroyed" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnWindowDestroyed(EventArgs e)
		{
			EventHandler handler = null;

			lock (WindowDestroyedEventLock)
			{
				handler = WindowDestroyedEvent;
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="Window"/> is created.
		/// </summary>
		public event EventHandler WindowCreated
		{
			add
			{
				lock (WindowCreatedEventLock)
				{
					WindowCreatedEvent += value;
				}
			}
			remove
			{
				lock (WindowCreatedEventLock)
				{
					WindowCreatedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="WindowCreated" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnWindowCreated(EventArgs e)
		{
			EventHandler handler = null;

			lock (WindowCreatedEventLock)
			{
				handler = WindowCreatedEvent;
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region Event Handlers
		private void WindowHiding(Window sourceWindow)
		{
			if (showing)
			{
				showing = false;
				OnHide(EventArgs.Empty);
			}
		}

		private void WindowActivated(Window gotFocus, Window lostFocus)
		{
			if (gotFocus == window)
			{
				if (!showing)
				{
					showing = true;
					OnShow(EventArgs.Empty);
				}

				hasFocus = true;
				OnActivated(EventArgs.Empty);
			}
			else if (showing && lostFocus == window)
			{
				hasFocus = false;
				OnDeactivated(EventArgs.Empty);
			}
		}
		#endregion
	}
}
